package org.e_gold.factory.export;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.log4j.Logger;
import org.e_gold.dao.CEventDAO;
import org.e_gold.dao.CountryDAO;
import org.e_gold.dao.LeagueDAO;
import org.e_gold.dao.UserAccountDAO;
import org.e_gold.factory.AppProperties;
import org.e_gold.model.SettledContract;
import org.e_gold.model.crawler.livescore.Country;
import org.e_gold.model.crawler.livescore.League;
import org.e_gold.util.DateUtil;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;

/**
 * @author Thang Nguyen
 */
@Component
@Scope("singleton")
public class MonitorThread extends Thread{

	private static final Logger logger = Logger.getLogger(MonitorThread.class);

	public LinkedList<Object> queue = new LinkedList<Object>();
	private String MONITOR_URL;
	private String MONITOR_KEY;
	private final String EXPORT_TYPE_USER_ACCOUNT = "UserAccount";
	private final String EXPORT_TYPE_SETTLED_CONTRACT = "SettledContract";
	private final String EXPORT_TYPE_CRAWL_DATA = "CrawlData";
	private final String USER_ACCOUNT_ID = "2";
	private int SLEEP_TIME = 30 * 1000;
	private final int EXPORT_CRAWLER_EVERY_TIMES = 10;
	private int EXPORT_CRAWLER_CURRENT = 0;
	private int EXPORTED_COUNT = 0;
	//	private final String CONFIG_SEPERATE = "xx00xx";

	private boolean initialized = false;

	@Autowired
	private AppProperties appProperties;

	@Autowired
	private CEventDAO cEventDAO;

	@Autowired
	private CountryDAO countryDAO;

	@Autowired
	private LeagueDAO leagueDAO;

	@Autowired
	private UserAccountDAO userAccountDAO;

	public void init(){
		MONITOR_URL = appProperties.getMonitorUrl();
		MONITOR_KEY = appProperties.getMonitorKey();
		try{
			SLEEP_TIME = appProperties.getMonitorSleep() * 1000;
		}catch(Exception ex){
			logger.error("Sleep time configuration wrong. Set to 10s");
		}
		initialized = true;
	}

	public void offer(Object e){
		logger.info("<<<<<<<<Monitor OFFERING: " + e.toString() + ">>>>>>>>>>>>>>>");

		//unique object only ^.^
		try{
			Iterator<Object> iter = queue.iterator();
			while(iter.hasNext()){
				Object obj = iter.next();
				if(obj.getClass().equals(e.getClass())){
					if(obj instanceof Float){
						//update latest balance
						iter.remove();
					}else if(obj instanceof SettledContract){
						//update latest settledContract
						SettledContract sc = (SettledContract) obj;
						SettledContract settledToUpdate = (SettledContract) e;
						if(sc.getId().equals(settledToUpdate.getId())){
							iter.remove();
						}
					}

				}
			}
		}catch(Exception ex){
			logger.info("Monitor offer bug!");
			logger.error(ToStringBuilder.reflectionToString(ex, ToStringStyle.SHORT_PREFIX_STYLE));
			ex.printStackTrace();
		}
		if(!queue.offer(e)){
			logger.error("Unabled to offer [" + e + "]");
		}
	}

	@Override
	public void run() {
		try{
			while(true){
				try{
					if(!initialized){
						init();
					}
					while(queue.peek() != null){
						Object obj = queue.poll();
						logger.info("<<<<<<<<Monitor EXPORTING: " + obj + ">>>>>>>>>>>>>>>");
						if(obj instanceof Float){
							updateBalance((Float) obj);
						}
						else if(obj instanceof SettledContract){
							SettledContract sc = (SettledContract) obj;
							updateSettledContract(sc);
						}
						if(++EXPORTED_COUNT == 5){
							break;
						}
					}
					EXPORTED_COUNT = 0;
					checkAndExportCrawler();
					Thread.sleep(SLEEP_TIME);
				}catch(Exception ex){
					logger.info("Monitor queue bug!");
					logger.error(ToStringBuilder.reflectionToString(ex, ToStringStyle.SHORT_PREFIX_STYLE));
					ex.printStackTrace();
					Thread.sleep(3000);
				}
			}
		}catch(Exception ex){
			logger.info("Monitor stopped!!!");
			logger.error(ToStringBuilder.reflectionToString(ex, ToStringStyle.SHORT_PREFIX_STYLE));
			ex.printStackTrace();
		}
	}

	private void checkAndExportCrawler(){
		try{
			if(EXPORT_CRAWLER_CURRENT % EXPORT_CRAWLER_EVERY_TIMES == 0){
				long startTime = System.currentTimeMillis();
				logger.info("<<<Begin checkAndExportCrawler");
				String strCountry = "";

				List<Country> lstCountry = countryDAO.getNearlyCountry(0.5);
				List<League> lstLeague = leagueDAO.getNearlyLeague(0.5);
				//			List<CEvent> lstCEvent = cEventDAO.findAll();
				Gson gb =  new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
				ExportInfo info = new ExportInfo();

				if(!CollectionUtils.isEmpty(lstCountry)){
					Collections.sort(lstCountry);
					info.lstCountry = lstCountry;
					if(!CollectionUtils.isEmpty(lstLeague)){
						info.lstLeague = lstLeague;
					}
				}
				strCountry = gb.toJson(info.getExportData());
				//				System.out.println(strCountry);
				updateCrawl(strCountry);
				logger.info("<<<End checkAndExportCrawler: " + ( (System.currentTimeMillis() - startTime) / 1000));
			}
		}catch(Exception ex){
			logger.info("checkAndExportCrawler failed!!!");
			logger.error(ToStringBuilder.reflectionToString(ex, ToStringStyle.SHORT_PREFIX_STYLE));
			ex.printStackTrace();
		}
		EXPORT_CRAWLER_CURRENT++;
	}

	private void updateCrawl(String data) throws Exception{
		try{
			Map<String, String> formData = new HashMap<String, String>();
			formData.put("type", EXPORT_TYPE_CRAWL_DATA);
			formData.put("data", data);

			String ret = post(MONITOR_URL, formData);
			if(ret.equals("1")){
				logger.info("updateCrawl completed successfully");
			}else{
				logger.info("updateCrawl: " + ret);
				logger.error("updateCrawl: " + ret);
			}
		}catch(Exception ex){
			logger.error(ToStringBuilder.reflectionToString(ex, ToStringStyle.SHORT_PREFIX_STYLE));
			throw ex;
		}
	}

	private void updateBalance(Float balance) throws Exception{
		try{
			Map<String, String> formData = new HashMap<String, String>();
			formData.put("type", EXPORT_TYPE_USER_ACCOUNT);
			formData.put("userId", USER_ACCOUNT_ID);
			formData.put("balance", balance.toString());

			export_monitor(formData);
		}catch(Exception ex){
			logger.error(ToStringBuilder.reflectionToString(ex, ToStringStyle.SHORT_PREFIX_STYLE));
			throw ex;
		}

	}

	private void updateSettledContract(SettledContract sc) throws Exception{
		try{
			Map<String, String> formData = new HashMap<String, String>();
			formData.put("type", EXPORT_TYPE_SETTLED_CONTRACT);
			formData.put("id", sc.getId().toString());
			formData.put("insert_timestamp", DateUtil.getDate(sc.getInsertTimeStamp()));
			formData.put("modify_timestamp", DateUtil.getDate(sc.getModifyTimeStamp()));
			formData.put("bet_amount", sc.getBetAmount().toString());
			formData.put("bet_slip", sc.getBetSlip());
			formData.put("current_goal", sc.getCurrentGoal().toString());
			formData.put("dangerous", sc.getDangerous().toString());
			formData.put("result_amount", sc.getResultAmount().toString());
			formData.put("running", sc.getRunning().toString());
			formData.put("settled_play_time", sc.getSettledPlayTime().toString());
			formData.put("settled_total_goals", sc.getSettledTotalGoals().toString());
			formData.put("under_goal", sc.getUnderGoal().toString());
			formData.put("won", sc.getWon().toString());
			formData.put("won_amount", sc.getWonAmount().toString());
			formData.put("contract_id", sc.getContract().getId().toString());
			formData.put("home_name", sc.getContract().getEvent().getHomeName());
			formData.put("away_name", sc.getContract().getEvent().getAwayName());
			formData.put("current_time", sc.getContract().getEvent().getPlayTime().toString());
			formData.put("rate", String.valueOf((sc.getWonAmount() / sc.getBetAmount())) );
			formData.put("league_id", sc.getContract().getEvent().getLeague().getId().toString());

			export_monitor(formData);
		}catch(Exception ex){
			logger.error(ToStringBuilder.reflectionToString(ex, ToStringStyle.SHORT_PREFIX_STYLE));
			throw ex;
		}
	}

	private void export_monitor(Map<String, String> formData) {
		String ret = post(MONITOR_URL, formData);
		int status = 0;
		try{
			status = (Integer.valueOf(ret));
		}catch(Exception e){
			logger.error(e);
			e.printStackTrace();
		}
		if(status == 1){
			logger.info("<<<<<<<<Monitor exported [" + formData + "]" + ">>>>>>>>>>>>>>>");
		}else{
			logger.info("<<<<<<<<Monitor Unabled to export [" + ret + "]: " + formData + ">>>>>>>>>>>>>>>");
		}
	}

	private String post(String url, Map<String, String> formData){
		String str = "";
		try {
			formData.put("validate", MONITOR_KEY);
			Connection connection = Jsoup.connect(url);
			connection.timeout(300000).data(formData);
			Document doc = connection.post();

			str = doc.body().text();
		} catch(NumberFormatException nfe){
		} catch (Exception e) {
			logger.error(e);
			e.printStackTrace();
		}
		return str;
	}

	private final class ExportInfo{
		@Expose
		private List<Country> lstCountry;

		List<League> lstLeague;

		public List<Country> getExportData(){

			Map<String, Country> mCountry = new HashMap<String, Country>();
			for(Country c : lstCountry){
				mCountry.put(c.getName(), c);
			}

			for(League l : lstLeague){
				String countryName = l.getCountry().getName();
				Country ct = mCountry.get(countryName);
				Set<League> sLeague = ct.getLeagues();
				sLeague.add(l);
			}

			return new ArrayList<Country>(mCountry.values());
		}
	}
}
